
#include "telnet_session.h"
#include "registercenter.hpp"
#include "sessionmanager.h"

#include <iostream>
#include <string>
#include <stdarg.h> 
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>

using namespace boost::asio;
using namespace boost::system;
using boost::asio::ip::tcp;

std::string TelnetSession::_username;
std::string TelnetSession::_passwd;

TelnetSession::TelnetSession(boost::asio::io_service& io_service)
    : _socket(io_service)
{
}

// 入口函数
void TelnetSession::start()
{
    std::cout << "accepted one session.\n";
    SessionManager::instance().join(this);
    // 设置telnet属性
    sendIAC(TELCMD_DO, TELOPT_ECHO);
    sendIAC(TELCMD_DO, TELOPT_NAWS);
    sendIAC(TELCMD_DO, TELOPT_LFLOW);
    sendIAC(TELCMD_WILL, TELOPT_ECHO);
    sendIAC(TELCMD_WILL, TELOPT_SGA);

    // 输出欢迎画面 
    telnetPrint("*===============================================================\n");
    telnetPrint("Welcome to the telnet server\n");
    telnetPrint("*===============================================================\n");

    _promtState = PROMTUSER;
    // 显示提示符
    promptShow();   
    telnetEcho();
}


void TelnetSession::telnetPrint(char *szFormat, ...)
{
    const uint32_t MAX_LOG_MSG_LEN = 6000;  // 每条日志的最大长度
    va_list pvList;
    char msg[MAX_LOG_MSG_LEN] = { 0 };
    uint32_t len = 0;

    if (szFormat == NULL)
        return;
    va_start(pvList, szFormat);
    len = vsprintf(msg, szFormat, pvList);
    va_end(pvList);
    if (len <= 0 || len >= MAX_LOG_MSG_LEN - 2)
        return;
    memcpy(msg + len, "\r\n", 2);
    len += 2;
    doTcpWrite(msg, len);
}


enum tel_state {
    tel_normal = 0,
    tel_nego = 1,
    tel_sub = 2
};
static int32_t seen_iac = 0;
static enum tel_state state = tel_normal;
static int32_t count_after_sb = 0;
static char removeIAC(uint8_t c)
{
    char ret = 0;
    if ((c == 255) && !seen_iac)    // IAC
    {
        seen_iac = 1;
        return ret;
    }

    if (seen_iac)
    {
        switch (c)
        {
        case 251:
        case 252:
        case 253:
        case 254:
            if (state != tel_normal)
                std::cout << " illegal negotiation.\n";
            state = tel_nego;
            break;
        case 250:
            if (state != tel_normal)
                std::cout << " illegal sub negotiation.\n";
            state = tel_sub;
            count_after_sb = 0;
            break;
        case 240:
            if (state != tel_sub)
                std::cout << " illegal sub end.\n";
            state = tel_normal;
            break;
        default:
            if (!((c > 240) && (c < 250) && (state == tel_normal)))
                std::cout << "illegal command.\n";
            state = tel_normal;
        }
        seen_iac = 0;
        return '\0';
    }

    switch (state)
    {
    case tel_nego:
        state = tel_normal;
        break;
    case tel_sub:
        count_after_sb++; // set maximize sub negotiation length
        if (count_after_sb >= 100) state = tel_normal;
        break;
    default:
        ret = c;
    }
    return ret;
}

enum ArrowTriggerState
{
    invalid_status = 0,
    receive_27 = 1,
    receive_91 = 2,
    receive_char = 3
};

// 读取用户输入并回显
void TelnetSession::telnetEcho()
{
    size_t read_ret = -1;
    char cmdChar;
    char command[MAX_COMMAND_LENGTH] = { 0 };
    std::string tmpCmd;
    uint32_t cmdLen = 0;
    char prevCmdChar = '\0';
    ArrowTriggerState arrowTrigger = invalid_status;  // 是否收到27键值，27键值为方向键的初始值

    auto eraseOneChar = [this]() {
        // 使光标后退，用一个空格擦除原字符，再使光标后退
        char tmpChar[3];
        tmpChar[0] = BACKSPACE_CHAR;
        tmpChar[1] = BLANK_CHAR;
        tmpChar[2] = BACKSPACE_CHAR;
        telnetCmdEcho(tmpChar, 3);
    };


    auto doArrowTrigger = [&command, &cmdLen, &cmdChar, eraseOneChar, this]() {
        if (cmdChar == 'A')    // 上 [A
        {
            if (_lastestCmdIndex > 0)
                _lastestCmdIndex -= 1;
        }
        else if (cmdChar == 'B')    // 下[B
        {
            if (_lastestCmdIndex < _lastCommands.size())
                _lastestCmdIndex += 1;
        }
        else if (cmdChar == 'C' || cmdChar == 'D') // [C右 [D左
        {
            return;   // 不显示该字符
        }

        for (uint32_t i = 0; i < cmdLen; i++)
        {
            eraseOneChar();
        }

        std::string tmpCmd = _lastCommands.get(_lastestCmdIndex);
        telnetCmdEcho(tmpCmd.c_str(), tmpCmd.length());
        memset(command, 0, MAX_COMMAND_LENGTH);
        memcpy(command, tmpCmd.c_str(), tmpCmd.length());
        cmdLen = tmpCmd.length();
    };


    while (true)
    {
        try
        {
            if (!_socket.is_open())
            {
                doTcpClose();
                return;
            }
                
            read_ret = boost::asio::read(_socket, boost::asio::buffer(&cmdChar, 1));
        }
        catch (const std::exception&)
        {
            doTcpClose();
            return;
        }
        if (read_ret == 0)   // 客户端关闭 
        {
            doTcpClose();
            return;
        }

        cmdChar = removeIAC((uint8_t)cmdChar);
        // 解析用户输入, 回显到Telnet客户端屏幕上, 对于命令给出适当的响应
        switch (cmdChar)
        {
        case CTRL_S:
            _isStopTelnetPrint = true;
            telnetPrint("\nScreen log disabled temporarily. Use ^R to resume it if necessary.\n\n");
            promptShow();
            break;

        case CTRL_R:
            _isStopTelnetPrint = false;
            telnetPrint("\nScreen log resumed again.\n");
            promptShow();
            break;

        case RETURN_CHAR:           // 回车符
            telnetCmdEcho("\r\n", 2);
            if (arrowTrigger == receive_char)   // 先删除历史列表中的命令
            {
                _lastCommands.erase(command);
                arrowTrigger = invalid_status;
            }
            runCmd(command);

            cmdLen = 0;
            memset(command, 0, MAX_COMMAND_LENGTH);
            promptShow();           // 显示提示符
            break;

        case NEWLINE_CHAR:		    // 换行符 
            break;
        case LEFT_ARROW:            // 左箭头
        case RIGHT_ARROW:           // 右箭头
        case UP_ARROW:              // 上箭头
        case DOWN_ARROW:            // 下箭头
            arrowTrigger = receive_27;
            break;
        case LEFT_SQUARE:
            if (arrowTrigger == receive_27)
            { 
                arrowTrigger = receive_91;
                break;
            }

        case DEL_CHAR:              // Del
        case BACKSPACE_CHAR:        // 退格键
            if (cmdLen <= 0)
                continue;

            cmdLen--;
            if (cmdLen >= 0 && cmdLen < MAX_COMMAND_LENGTH)
                command[cmdLen] = '\0';

            if (_promtState != PROMTPWD)
            {
                // 使光标后退，用一个空格擦除原字符，再使光标后退
                eraseOneChar();
            }
            break;

        default:
            if (arrowTrigger == receive_91)
            {
                doArrowTrigger();
                arrowTrigger = receive_char;
                break;
            }
            // 回显输入的字符，密码不显示
            if (_promtState != PROMTPWD)
                telnetCmdEcho(&cmdChar, 1);

            if (cmdLen < MAX_COMMAND_LENGTH)
            {
                if (cmdLen == 0 && cmdChar == '\0')
                    break;
                command[cmdLen] = cmdChar;
                cmdLen += 1;
            }
            else
            {
                telnetPrint("command is too long");
                promptShow();         // 显示提示符
                cmdLen = 0;
                memset(command, 0, MAX_COMMAND_LENGTH);
            }
            break;
        }
    }
}
// 检查用户的授权,并作相应状态处理.
void TelnetSession::checkAuth(const std::string& cmd)
{
    if (_promtState == PROMTAUTHORIZED) // 已认证
        return;

    if (cmd.length() > 0)
    {
        switch (_promtState)
        {
        case PROMTUSER:
        {
            if (cmd == _username)
            {
                _isAuthPass = true;
            }
            else
            {
                _isAuthPass = false;
            }
            _promtState = PROMTPWD;
            break;
        }
        case PROMTPWD:
        {
            if (cmd == _passwd)
            {
                if (_isAuthPass == true)
                {
                    _promtState = PROMTAUTHORIZED;
                }
                else
                {
                    _promtState = PROMTUSER;
                }
            }
            else
            {
                _isAuthPass = false;
                _promtState = PROMTUSER;
            }
            break;
        }
        default:
            break;
        }
    }
    else
    {
        switch (_promtState)
        {
        case PROMTUSER:
        {
            if (cmd == _username)
            {
                _isAuthPass = true;
            }
            else
            {
                _isAuthPass = false;
            }
            _promtState = PROMTPWD;
            break;
        }
        case PROMTPWD:
        {
            if (cmd == _passwd)
            {
                if (_isAuthPass == true)
                {
                    _promtState = PROMTAUTHORIZED;
                }
                else
                {
                    _promtState = PROMTUSER;
                }
            }
            else
            {
                _isAuthPass = false;
                _promtState = PROMTUSER;
            }
            break;
        }
        default:
            break;
        }
    }
}

void TelnetSession::runCmd(const char *szCmd)
{

    uint8_t index = 0;
    uint8_t chStartIndex = 0;
    bool isStrStart = false;                // 处理'\"'情况
    bool isCharStart = false;               // 处理'\''情况
    std::vector<std::string> args_vec;      // 参数列表

    std::string cmd(szCmd);
    _lastCommands.push(cmd);
    _lastestCmdIndex = _lastCommands.size();

    cmd = boost::algorithm::trim_copy(cmd); // 清楚两端空格
    uint32_t cmdLen = cmd.length() + 1;

    if (_promtState == PROMTUSER || _promtState == PROMTPWD)
    {
        checkAuth(cmd);
    }
    else if (_promtState == PROMTAUTHORIZED)
    {
        // 解析参数、命令
        while (index < cmdLen)
        {
            switch (cmd[index])
            {
            case '\'':
                cmd[index] = ' ';
                if (!isCharStart)
                {
                    chStartIndex = index;
                }
                else
                {
                    if (index > chStartIndex + 2)
                    {
                        telnetPrint("input error.\n");
                        return;
                    }
                }
                isCharStart = !isCharStart;
                break;

            case '\"':
                cmd[index] = ' ';
                isStrStart = !isStrStart;
                break;

            case ',':
            case ' ':
            case '\t':
            case '\n':
            case '(':
            case ')':
                if (!isStrStart)
                {
                    cmd[index] = ' ';
                }
                break;
            }
            index++;
        }

        if (isStrStart || isCharStart)
        {
            telnetPrint("String mismatch, input error.\n");
            return;
        }
        if (cmd.length() == 0)
            return;

        // 按空格拆分输入参数
        boost::split(args_vec, cmd, boost::is_any_of(" "), boost::token_compress_on);
        std::string cmd_key = args_vec[0];   // 遇' '截断
        // 先执行内置命令 
        if (cmd_key.compare("exit") == 0)
        {
            telnetPrint("\n  exit......\n");
            doTcpClose();
            return;
        }
        if (cmd_key.compare("help") == 0)
        {
            osphelp();
            return;
        }
        if (cmd_key.compare("all") == 0)
        {
            auto allcmd = RegCenter::instance().showall();
            for (auto& cmd : allcmd)
            { 
                telnetPrint((char*)cmd.c_str());
            }
            return;
        }

        auto args_num = args_vec.size();
        if (args_num == 1)
            RegCenter::instance().sendMessage(cmd_key);
        else if (args_num == 2)
            RegCenter::instance().sendMessage(cmd_key, args_vec[1]);
        else if (args_num == 3)
            RegCenter::instance().sendMessage(cmd_key, args_vec[1], args_vec[2]);
        else if (args_num == 4)
            RegCenter::instance().sendMessage(cmd_key, args_vec[1], args_vec[2], args_vec[3]);
        else if (args_num == 5)
            RegCenter::instance().sendMessage(cmd_key, args_vec[1], args_vec[2], args_vec[3], args_vec[4]);
    }
}

// 在Telnet上显示提示符.
void TelnetSession::promptShow(void)
{
    char prompt[128] = {0};	// "%s->"
    static const char* authedPromot = "tel->";
    switch (_promtState)
    {
        case PROMTUSER:
        {
            sprintf(prompt, "Username:");
            telnetCmdEcho(prompt, strlen(prompt) + 1);
            break;
        }
        case PROMTPWD:
        {
            sprintf(prompt, "Password:");
            telnetCmdEcho(prompt, strlen(prompt) + 1);
            break;
        }
        case PROMTAUTHORIZED:
        {
            // 取得当前进程所在的模块名，作为Telnet客户的提示符 
            //sprintf(prompt, "%s->", boost::filesystem::current_path().stem().string().c_str());
            telnetCmdEcho(authedPromot, strlen(authedPromot) + 1);
            break;
        }
        default:
        {
            telnetPrint("Telnet Prompt State error!\n"); 
            break;
        }
    }
}

// 向Telnet客户端屏幕回显用户命令
// szCmd: 命令串
// cmdLen: 命令长度
void TelnetSession::telnetCmdEcho(const char* szCmd, uint32_t cmdLen)
{
    doTcpWrite(szCmd, cmdLen);
}

size_t TelnetSession::doTcpWrite(const char* data, int len)
{
    if (_isStopTelnetPrint)
        return 0;

    size_t ret = 0;
    try
    {
        ret = boost::asio::write(_socket, boost::asio::buffer(data, len));
    }
    catch (const std::exception&)
    {
        return ret;
    }
    return ret;
}

void TelnetSession::doTcpClose()
{
    std::cout << "closed one session.\n";

    SessionManager::instance().leave(this);
    boost::system::error_code ignored_ec;
    _socket.close(ignored_ec);
}

// 设置telnet属性
void TelnetSession::sendIAC(char cmd, char opt)
{
    uint8_t buf[5];
    buf[0] = TELCMD_IAC;
    buf[1] = cmd;
    buf[2] = opt;
    doTcpWrite((char*)buf, 3);
}

// 显示本模块的外部用户注册帮助信息
void TelnetSession::osphelp(void)
{
    telnetPrint("Compile Time: %s  %s\n", __TIME__, __DATE__);
    telnetPrint("exit - close the telnet session and exit.\n");
    telnetPrint("all - show all custom commands.\n");
}
